package com.glink.common.util;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

public class ListKit {

    // region toMap
    public static <T, K, U> Map<K, U> toMap(List<? extends T> list,
                                            Predicate<? super T> filter,
                                            Function<? super T, ? extends K> keyMapper,
                                            Function<? super T, ? extends U> valMapper) {
        if (isNull(list) || list.isEmpty()) {
            return Collections.emptyMap();
        }
        Stream<? extends T> stream = list.stream();
        if (nonNull(filter)) {
            stream = stream.filter(filter);
        }
        return stream.collect(Collectors.toMap(keyMapper, valMapper, (v1, v2) -> v2));
    }

    public static <T, K, U> Map<K, U> toMap(List<? extends T> list,
                                            Function<? super T, ? extends K> keyMapper,
                                            Function<? super T, ? extends U> valMapper) {
        return toMap(list, Objects::nonNull, keyMapper, valMapper);
    }

    public static <K, U> Map<K, U> toMap(List<? extends U> list,
                                         Function<? super U, ? extends K> keyMapper) {
        return toMap(list, Objects::nonNull, keyMapper, x -> x);
    }
    // endregion toMap

    // region cast(mapping)
    public static <E, R> List<R> cast(List<? extends E> list,
                                      Predicate<? super E> filter,
                                      Function<? super E, ? extends R> mapper,
                                      boolean distinct) {
        if (isNull(list) || list.isEmpty()) {
            return Collections.emptyList();
        }
        Stream<? extends E> input = list.stream();
        if (nonNull(filter)) {
            input = input.filter(filter);
        }
        Stream<? extends R> output = input.map(mapper);
        if (distinct) {
            output = output.distinct();
        }
        return output.collect(Collectors.toList());
    }

    public static <E, R> List<R> cast(List<? extends E> list,
                                      Function<? super E, ? extends R> mapper,
                                      boolean distinct) {
        return cast(list, Objects::nonNull, mapper, distinct);
    }

    public static <E, R> List<R> cast(List<? extends E> list,
                                      Function<? super E, ? extends R> mapper) {
        return cast(list, Objects::nonNull, mapper, false);
    }

    public static <E, R> List<R> mapping(List<? extends E> list,
                                         Predicate<? super E> filter,
                                         Function<? super E, ? extends R> mapper,
                                         boolean distinct) {
        return cast(list, filter, mapper, distinct);
    }

    public static <E, R> List<R> mapping(List<? extends E> list,
                                         Function<? super E, ? extends R> mapper,
                                         boolean distinct) {
        return cast(list, Objects::nonNull, mapper, distinct);
    }

    public static <E, R> List<R> mapping(List<? extends E> list,
                                         Function<? super E, ? extends R> mapper) {
        return cast(list, Objects::nonNull, mapper, false);
    }
    // endregion cast

    // region findAny
    public static <E, R> Optional<R> findAnyOpt(List<E> list,
                                                Predicate<? super E> filter,
                                                Function<? super E, R> mapper) {
        if (isNull(list) || list.isEmpty()) {
            return Optional.empty();
        }
        Stream<E> stream = list.stream();
        if (nonNull(filter)) {
            stream = stream.filter(filter);
        }
        return stream.map(mapper).findAny();
    }

    public static <E> Optional<E> findAnyOpt(List<E> list,
                                             Predicate<? super E> filter) {
        return findAnyOpt(list, filter, x -> x);
    }

    public static <E, R> R findAny(List<E> list,
                                   Predicate<? super E> filter,
                                   Function<? super E, R> mapper) {
        return findAnyOpt(list, filter, mapper).orElse(null);
    }

    public static <E> E findAny(List<E> list,
                                Predicate<? super E> filter) {
        return findAnyOpt(list, filter, x -> x).orElse(null);
    }
    // endregion findAny

    // region findFirst
    public static <E, R> Optional<R> findFirstOpt(List<E> list,
                                                  Predicate<? super E> filter,
                                                  Function<? super E, R> mapper) {
        if (isNull(list) || list.isEmpty()) {
            return Optional.empty();
        }
        Stream<E> stream = list.stream();
        if (nonNull(filter)) {
            stream = stream.filter(filter);
        }
        return stream.map(mapper).findFirst();
    }

    public static <E> Optional<E> findFirstOpt(List<E> list,
                                               Predicate<? super E> filter) {
        return findFirstOpt(list, filter, x -> x);
    }

    public static <E, R> R findFirst(List<E> list,
                                     Predicate<? super E> filter,
                                     Function<? super E, R> mapper) {
        return findFirstOpt(list, filter, mapper).orElse(null);
    }

    public static <E> E findFirst(List<E> list,
                                  Predicate<? super E> filter) {
        return findFirstOpt(list, filter, x -> x).orElse(null);
    }
    // endregion findFirst

    // region sum
    public static <E> Integer summingInt(List<E> list, Function<? super E, Integer> mapper) {
        if (isNull(list) || list.isEmpty()) {
            return 0;
        }
        return list.stream().filter(Objects::nonNull).map(mapper)
                .filter(Objects::nonNull).reduce(0, Integer::sum);
    }

    public static <E> Long summingLong(List<E> list, Function<? super E, Long> mapper) {
        if (isNull(list) || list.isEmpty()) {
            return 0L;
        }
        return list.stream().filter(Objects::nonNull).map(mapper)
                .filter(Objects::nonNull).reduce(0L, Long::sum);
    }
    // endregion sum

    // region group
    public static <T, K, A, D> Map<K, D> groupingBy(List<T> list,
                                                    Predicate<? super T> filter,
                                                    Function<? super T, ? extends K> classifier,
                                                    Collector<? super T, A, D> downstream) {
        if (isNull(list) || list.isEmpty()) {
            return Collections.emptyMap();
        }
        Stream<T> stream = list.stream();
        if (nonNull(filter)) {
            stream = stream.filter(filter);
        }
        return stream.collect(Collectors.groupingBy(classifier, downstream));
    }

    public static <T, K> Map<K, Integer> summingIntGroupingBy(List<T> list,
                                                              Function<? super T, ? extends K> classifier,
                                                              ToIntFunction<? super T> mapper) {
        return groupingBy(list, null, classifier, Collectors.summingInt(mapper));
    }

    public static <T, K> Map<K, Long> summingLongGroupingBy(List<T> list,
                                                            Function<? super T, ? extends K> classifier,
                                                            ToLongFunction<? super T> mapper) {
        return groupingBy(list, null, classifier, Collectors.summingLong(mapper));
    }

    public static <T, K> Map<K, Integer> summingIntGroupingBy(List<T> list,
                                                              Predicate<? super T> filter,
                                                              Function<? super T, ? extends K> classifier,
                                                              Function<? super T, Integer> mapper) {
        return groupingBy(list, filter, classifier,
                Collectors.mapping(mapper, Collectors.reducing(0, Integer::sum)));
    }

    public static <T, K> Map<K, Long> summingLongGroupingBy(List<T> list,
                                                            Predicate<? super T> filter,
                                                            Function<? super T, ? extends K> classifier,
                                                            Function<? super T, Long> mapper) {
        return groupingBy(list, filter, classifier,
                Collectors.mapping(mapper, Collectors.reducing(0L, Long::sum)));
    }
    // endregion group

    // region match
    public static <E> boolean containsDuplicate(List<E> list) {
        if (isNull(list) || list.isEmpty()) {
            return false;
        }
        return list.size() > new HashSet<>(list).size();
    }

    public static <E, R> boolean anyMatch(List<E> list,
                                          Function<? super E, ? extends R> mapper,
                                          Predicate<? super R> matcher) {
        if (isNull(list) || list.isEmpty()) {
            return false;
        }
        return list.stream().map(mapper).anyMatch(matcher);
    }

    public static <E> boolean anyMatch(List<E> list, Predicate<? super E> matcher) {
        if (isNull(list) || list.isEmpty()) {
            return false;
        }
        return list.stream().anyMatch(matcher);
    }

    public static <E, R> boolean allMatch(List<E> list,
                                          Function<? super E, ? extends R> mapper,
                                          Predicate<? super R> matcher) {
        if (isNull(list) || list.isEmpty()) {
            return false;
        }
        return list.stream().map(mapper).allMatch(matcher);
    }

    public static <E> boolean allMatch(List<E> list, Predicate<? super E> matcher) {
        if (isNull(list) || list.isEmpty()) {
            return false;
        }
        return list.stream().allMatch(matcher);
    }
    // endregion match

}